package replay

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"hash/crc32"
	"io"
	"os"
	"path/filepath"
	"sync"
	"time"
)

// TimeRotatingRecorder 支持时间占位符替换的记录器
type TimeRotatingRecorder struct {
	saveFormat     string     // 原始保存格式字符串
	baseDir        string     // 基本路径不解析
	currentFile    *os.File   // 当前打开的文件
	currentPath    string     // 当前文件路径
	currentStart   int64      // 当前开始时间
	mu             sync.Mutex // 线程安全锁
	protocolPrefix []byte     // 协议前缀

	// truncate regular writable file when opened.
	OpenFileFlag int

	OnNewFile   func(filename string, w io.Writer)
	OnCloseFile func(filename string, w io.Writer)
}

func NewTimeRotatingRecorder() *TimeRotatingRecorder {
	r := &TimeRotatingRecorder{}
	return r
}

// NewRecorder 创建或打开文件用于追加记录.
func NewRecorder(fileName string) (*TimeRotatingRecorder, error) {
	r := NewTimeRotatingRecorder()
	r.SetBaseDir(fileName)
	return r, nil
}

// SetProtocolPrefix 设置协议前缀
func (r *TimeRotatingRecorder) SetProtocolPrefix(prefix []byte) {
	r.protocolPrefix = make([]byte, len(prefix))
	copy(r.protocolPrefix, prefix)
}

func (r *TimeRotatingRecorder) SetSaveFormat(s string) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.saveFormat = s
}

func (r *TimeRotatingRecorder) SetBaseDir(s string) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.baseDir = s
}

func (r *TimeRotatingRecorder) Write(data []byte) (n int, err error) {
	err = r.WriteWithNowTime(time.Now(), data)
	n = len(data)
	return
}

// Write 写入带外部时间戳的数据，自动按时间切换文件
func (r *TimeRotatingRecorder) WriteWithNowTime(nowTime time.Time, data []byte) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	// 生成当前时间对应的文件路径
	targetPath, err := r.generateFilePath(nowTime)
	if err != nil {
		return fmt.Errorf("生成目标文件路径失败: %v", err)
	}

	// 检查是否需要切换文件
	if targetPath != r.currentPath {
		if err := r.closeCurrentFile(); err != nil {
			return fmt.Errorf("close current file fail, %v", err)
		}
		if err := r.openFile(targetPath, nowTime); err != nil {
			return fmt.Errorf("open new file fail, %v", err)
		}
	}

	// 写入数据（时间戳+长度+数据）
	offset := uint32(nowTime.UnixNano() - r.currentStart)
	if err := binary.Write(r.currentFile, binary.LittleEndian, offset); err != nil {
		return err
	}
	if err := binary.Write(r.currentFile, binary.LittleEndian, uint32(len(data))); err != nil {
		return err
	}
	if _, err := r.currentFile.Write(data); err != nil {
		return err
	}

	return nil
}

// GetCurrentFile 返回当前正在写入的文件完整路径
func (r *TimeRotatingRecorder) GetCurrentFile() string {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.currentPath
}

// GetSaveFormat 返回创建时使用的原始保存格式
func (r *TimeRotatingRecorder) GetSaveFormat() string {
	return r.saveFormat
}

// Close 关闭记录器
func (r *TimeRotatingRecorder) Close() error {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.closeCurrentFile()
}

// 生成带时间戳的文件路径
func (r *TimeRotatingRecorder) generateFilePath(t time.Time) (string, error) {
	path := ParseTimeFormat(r.saveFormat, t)

	if len(r.baseDir) > 0 {
		path = filepath.Join(r.baseDir, path)
	}

	// 创建所需的目录
	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return "", fmt.Errorf("make dir fail, %v", err)
	}

	return path, nil
}

func (r *TimeRotatingRecorder) doNewFile(filename string, nowTime time.Time, w io.Writer) error {

	var bb bytes.Buffer
	bb.Write(r.protocolPrefix)
	binary.Write(&bb, binary.LittleEndian, nowTime.UnixNano())
	crc := crc32.ChecksumIEEE(bb.Bytes())
	binary.Write(&bb, binary.LittleEndian, crc)

	// 如果设置了协议前缀，先写入前缀
	if _, err := r.currentFile.Write(bb.Bytes()); err != nil {
		return err
	}

	evt := r.OnNewFile
	if evt != nil {
		evt(filename, r.currentFile)
	}

	return nil
}

// 打开文件并更新当前状态
func (r *TimeRotatingRecorder) openFile(path string, nowTime time.Time) error {
	var f *os.File
	var err error
	if r.OpenFileFlag != 0 {
		f, err = os.OpenFile(path, r.OpenFileFlag, 0644)
	} else {
		f, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	}

	if err != nil {
		return err
	}

	r.currentFile = f
	r.currentPath = path

	err = r.doNewFile(path, nowTime, f)
	if err != nil {
		return err
	}

	r.currentStart = nowTime.UnixNano()
	return nil
}

// 关闭当前文件
func (r *TimeRotatingRecorder) closeCurrentFile() error {
	if r.currentFile != nil {
		evt := r.OnCloseFile
		if evt != nil {
			evt(r.currentPath, r.currentFile)
		}

		err := r.currentFile.Close()
		r.currentFile = nil
		r.currentPath = ""
		return err
	}
	return nil
}
